{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {
    "tags": []
   },
   "source": [
    "# How to style Buckaroo tables\n",
    "Buckaroo had a major refactoring of the styling system and callbacks with the 0.6 release.\n",
    "\n",
    "This notebook walks through\n",
    "* Styling columns via the `displayer`\n",
    "* How to override columns\n",
    "* Tooltips\n",
    "* Cell Coloring\n",
    "* Column hiding\n",
    "* pinned_rows\n",
    "* How to add automatic styling methods that are available via the UI to cycle through"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "from buckaroo.dataflow.styling_core import StylingAnalysis\n",
    "from buckaroo.pluggable_analysis_framework.col_analysis import ColAnalysis\n",
    "from buckaroo import BuckarooWidget"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "ROWS = 200\n",
    "typed_df = pd.DataFrame({'int_col':np.random.randint(1,50, ROWS), 'float_col': np.random.randint(1,30, ROWS)/.7,\n",
    "                         \"str_col\": [\"foobar\"]* ROWS})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "BuckarooWidget(typed_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4",
   "metadata": {},
   "source": [
    "## `displayer`\n",
    "Changing the `displayer` is the most common way to customize the styling of a column, in the next example, we override the column_config for `float_col`\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "bw2 = BuckarooWidget(\n",
    "    typed_df, \n",
    "    debug=False,\n",
    "    column_config_overrides={\n",
    "        'float_col':\n",
    "            {'displayer_args': { 'displayer': 'float', 'min_fraction_digits':0, 'max_fraction_digits':3}}})\n",
    "bw2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6",
   "metadata": {},
   "source": [
    "Now we are going to force `float_col` to be displayed with a 'float' displayer\n",
    "notice how the decimal point aligns as opposed to above where 10 is floored without a decimal portion\n",
    "\n",
    "Currently the types are best viewed in their typescript definition [DFWhole.ts](https://github.com/paddymul/buckaroo/blob/main/packages/buckaroo-js-core/src/components/DFViewerParts/DFWhole.ts)\n",
    "\n",
    "There are Displayers of\n",
    "\n",
    "`ObjDisplayer`, `BooleanDisplayer`, `StringDisplayer`, `FloatDisplayer`, \n",
    "`DatetimeDefaultDisplayer`, `DatetimeLocaleDisplayer`, `IntegerDisplayer`,\n",
    "\n",
    "`HistogramDisplayer`, and `LinkifyDisplayer`,\n",
    "\n",
    "There are planned displayers of [HumanAbbreviationDisplayer](https://github.com/paddymul/buckaroo/issues/83), [LineChartDisplayer](https://github.com/paddymul/buckaroo/issues/210), [GoogleMapsLinkDisplayer](https://github.com/paddymul/buckaroo/issues/211) , [InlineMapDisplayer](https://github.com/paddymul/buckaroo/issues/212)\n",
    "\n",
    "\n",
    "There is experimental work building mirrored PyDantic types, and work to integrate this typechecking into Buckaroo.  There are also plans for a gallery of examples of the different options."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7",
   "metadata": {},
   "source": [
    "# `tooltip_config`\n",
    "\n",
    "There are tooltip_configs of simple summary_series available\n",
    "\n",
    "Tooltips are helpful for adding extra context to cells.  Particularly for noting errors or values changed via auto-cleaning\n",
    "\n",
    "Notice that `column_config_overrides` is merged with the existing column config from Buckaroo, every column still has a displayer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "bw3 = BuckarooWidget(\n",
    "    typed_df, \n",
    "    column_config_overrides={\n",
    "        'str_col':\n",
    "            {'tooltip_config': { 'tooltip_type':'simple', 'val_column': 'int_col'}}})\n",
    "bw3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9",
   "metadata": {},
   "source": [
    "# color_map_config\n",
    "\n",
    "Color_map_config controls coloring of columns.  \n",
    "* `color_map` uses the bins from histogram to show a values place in the distribution.  wit the `val_column` parameter, you can color one column based on another.\n",
    "* `color_when_not_null` hilights a cell when another row is not null.  This is meant for error highlighting,  the other column can be hidden\n",
    "* `color_from_column` bases the color of a cell based on the RGB value written to another column.  It is the most generic coloring option"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "bw3 = BuckarooWidget(\n",
    "    typed_df, \n",
    "    column_config_overrides={\n",
    "        'float_col': {'color_map_config': {\n",
    "          'color_rule': 'color_map',\n",
    "          'map_name': 'BLUE_TO_YELLOW',\n",
    "            'val_column':'float_col'\n",
    "        }}})\n",
    "bw3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "bw3 = BuckarooWidget(\n",
    "    typed_df, \n",
    "    column_config_overrides={\n",
    "        'int_col': {'color_map_config': {\n",
    "            'color_rule': 'color_map',\n",
    "            'map_name': 'DIVERGING_RED_WHITE_BLUE',\n",
    "            'val_column': 'float_col'\n",
    "        }}})\n",
    "bw3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12",
   "metadata": {},
   "source": [
    "# Hiding a column\n",
    "\n",
    "You can hide a column with `merge_rule:'hidden'`.  This removes that column from the column_config array.\n",
    "\n",
    "Column hiding can be used to keep data in a dataframe (sent to the table widget) for use as a tooltip, or color, but preventing display which would distract the user\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "bw_ = BuckarooWidget(\n",
    "    typed_df, \n",
    "    column_config_overrides={\n",
    "        'int_col': {'merge_rule': 'hidden'}})\n",
    "bw_"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14",
   "metadata": {},
   "source": [
    "# Pinned rows\n",
    "Pinned rows are visible in each view.  They read data from the summary data that is assembled from the [PluggableAnalysisFramework](https://buckaroo-data.readthedocs.io/en/latest/articles/pluggable.html).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "bw = BuckarooWidget(\n",
    "    typed_df, \n",
    "    pinned_rows=[\n",
    "        {'primary_key_val': 'dtype',     'displayer_args': {'displayer': 'obj' } },\n",
    "        {'primary_key_val': 'histogram', 'displayer_args': {'displayer': 'histogram' }},   \n",
    "    ])\n",
    "bw"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16",
   "metadata": {},
   "source": [
    "# Packaging and reusing styling configurations with the `StylingAnalysis` class\n",
    "Up to this point we have been hardcoding config overrides.  You can package sets of styling configuration with the `StylingAnalysis` class.  These configs can then be toggled in the UI.\n",
    "\n",
    "If you just want to return a fixed `DFViewerConfig` override the `style_columns` method.  Most likely though, you will want to overide the singular `style_column` method that gets `SingleColumnMetadata` and returns a `ColumnConfig`. \n",
    "The `StylingAnalysis` class is used to control the display of a column based on the column metadata.  \n",
    "\n",
    "return {'col_name':str(col), 'displayer_args': {'displayer': 'obj'}}\n",
    "\n",
    "\n",
    "This lets you customize based on metadata collected about a column.  This works with the [PluggableAnalysisFramework](https://buckaroo-data.readthedocs.io/en/latest/articles/pluggable.html),  you can specify required fields that are necessary.  Adding requirements like this garuntees that errors are spotted early.\n",
    "\n",
    "StylingAnalysis works for both Polars and Pandas because it only receives a dictionary with simple python values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class SimpleStylingAnalysis(StylingAnalysis):\n",
    "    pinned_rows = [{'primary_key_val': 'dtype', 'displayer_args': {'displayer': 'obj'}}]\n",
    "\n",
    "    #typing is still be worked out\n",
    "    #def style_column(col:str, column_metadata: SingleColumnMetadata) -> ColumnConfig:\n",
    "    @classmethod\n",
    "    def style_column(kls, col, column_metadata):\n",
    "        return {'col_name':str(col), 'displayer_args': {'displayer': 'obj'}}\n",
    "\n",
    "        \n",
    "    #what is the key for this in the df_display_args_dictionary\n",
    "    df_display_name = \"MyStyles\"\n",
    "def obj_(pkey):\n",
    "    return {'primary_key_val': pkey, 'displayer_args': { 'displayer': 'obj' } }\n",
    "def float_(pkey, digits=3):\n",
    "    return {'primary_key_val': pkey, \n",
    "            'displayer_args': { 'displayer': 'float', 'min_fraction_digits':digits, 'max_fraction_digits':digits}}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "18",
   "metadata": {},
   "source": [
    "## Toggling between styles\n",
    "In the following example, you can cycle between the two configs by clicking on \"main\".  Eventually I will add a Select (dropdown) box for this.  I personally prefer click to cycle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "#The following analysis only\n",
    "class SummaryStatsAnalysis(StylingAnalysis):\n",
    "    requires_summary = ['dtype', 'min', 'mode', 'mean', 'max', 'unique_count', 'distinct_count', 'empty_count']\n",
    "    pinned_rows = [obj_('dtype'), float_('min'), \n",
    "                   #float_('mode'), float_('mean'),  \n",
    "                   float_('max'), float_('unique_count', 0),\n",
    "                   float_('distinct_count', 0), float_('empty_count', 0)]\n",
    "    df_display_name = \"summary\"\n",
    "    data_key = \"empty\"\n",
    "    summary_stats_key= 'all_stats'\n",
    "\n",
    "sbw = BuckarooWidget(typed_df, debug=True)\n",
    "sbw.add_analysis(SummaryStatsAnalysis)\n",
    "\n",
    "sbw"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20",
   "metadata": {},
   "source": [
    "## Overriding styling from post_processing\n",
    "\n",
    "`PostProcessing` returns a tuple of Dataframe, extra column metadata.\n",
    "\n",
    "The default base styling class which all examples here extend, handles the key of `column_config_override` speically.\n",
    "\n",
    "This lets you build  a specialized dataframe along with specific styling rules.  Auto_cleaning and regular polars/pandas analysis can use the same facility. It is not recommended for regular analysis.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21",
   "metadata": {},
   "outputs": [],
   "source": [
    "class ColumnConfigOverride(ColAnalysis):\n",
    "    @classmethod\n",
    "    def post_process_df(kls, df):\n",
    "        return [df, {\n",
    "            'int_col':{\n",
    "                'column_config_override': {'color_map_config': {\n",
    "                    'color_rule': 'color_map',\n",
    "                    'map_name': 'BLUE_TO_YELLOW',\n",
    "                    'val_column':'int_col'\n",
    "                }}}}]\n",
    "    post_processing_method = \"override\"\n",
    "bw = BuckarooWidget(typed_df)\n",
    "bw.add_analysis(ColumnConfigOverride)\n",
    "bw            "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22",
   "metadata": {},
   "source": [
    "# Buckaroo internals related to styling\n",
    "this can help debug what is going on with styling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "#you can view the main display_args with the following statement,  this lets you check what is actually being sent to the frontend\n",
    "sbw.df_display_args['main']['df_viewer_config']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# it's annoying to type out all of those pinned rows, lets make some convienence functions\n",
    "def obj_(pkey):\n",
    "    return {'primary_key_val': pkey, 'displayer_args': { 'displayer': 'obj' } }\n",
    "\n",
    "def float_(pkey, digits=3):\n",
    "    return {'primary_key_val': pkey, \n",
    "            'displayer_args': { 'displayer': 'float', 'min_fraction_digits':digits, 'max_fraction_digits':digits}}\n",
    "\n",
    "class SummaryStatsAnalysis1(SimpleStylingAnalysis):\n",
    "    pinned_rows = [\n",
    "        { 'primary_key_val': 'dtype',     'displayer_args': { 'displayer': 'obj' } },\n",
    "        { 'primary_key_val': 'histogram', 'displayer_args': { 'displayer': 'histogram' }},   \n",
    "    ]\n",
    "    df_display_name = \"summary1\"\n",
    "    data_key = \"empty\"\n",
    "    summary_stats_key= 'all_stats'\n",
    "class SummaryStatsAnalysis(SimpleStylingAnalysis):\n",
    "    pinned_rows = [\n",
    "        obj_('dtype'),\n",
    "        float_('min'),\n",
    "        float_('mean'),\n",
    "        float_('max'),\n",
    "    ]\n",
    "    df_display_name = \"summary\"\n",
    "    data_key = \"empty\"\n",
    "    summary_stats_key= 'all_stats'\n",
    "base_a_klasses = BuckarooWidget.analysis_klasses.copy()\n",
    "base_a_klasses.extend([SummaryStatsAnalysis1, SummaryStatsAnalysis])\n",
    "class SummaryBuckarooWidget(BuckarooWidget):\n",
    "    analysis_klasses = base_a_klasses\n",
    "sbw = SummaryBuckarooWidget(typed_df)\n",
    "#also lets do some hacking so that we start with the summary stats view\n",
    "bstate = sbw.buckaroo_state.copy()\n",
    "bstate['df_display'] = 'summary1'\n",
    "sbw.buckaroo_state= bstate\n",
    "sbw"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class SummaryStatsAnalysis(SimpleStylingAnalysis):\n",
    "    pinned_rows = [\n",
    "        obj_('dtype'),\n",
    "        float_('min'),\n",
    "        #float_('median'),\n",
    "        float_('mean'),\n",
    "        float_('max'),\n",
    "        float_('unique_count', 0),\n",
    "        float_('distinct_count', 0),\n",
    "        float_('empty_count', 0)\n",
    "    ]\n",
    "    df_display_name = \"summary2\"\n",
    "    data_key = \"empty\"\n",
    "    summary_stats_key= 'all_stats'\n",
    "base_a_klasses = BuckarooWidget.analysis_klasses.copy()\n",
    "base_a_klasses.append(SummaryStatsAnalysis)\n",
    "class SummaryBuckarooWidget(BuckarooWidget):\n",
    "    analysis_klasses = base_a_klasses\n",
    "sbw = SummaryBuckarooWidget(typed_df)\n",
    "#also lets do some hacking so that we start with the summary stats view\n",
    "bstate = sbw.buckaroo_state.copy()\n",
    "bstate['df_display'] = 'summary'\n",
    "sbw.buckaroo_state= bstate\n",
    "sbw"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26",
   "metadata": {},
   "outputs": [],
   "source": [
    "class AdaptiveStyling(StylingAnalysis):\n",
    "    requires_summary = [\"histogram\", \"is_numeric\", \"dtype\", \"is_integer\"]\n",
    "    pinned_rows = [\n",
    "        obj_('dtype'),\n",
    "        {'primary_key_val': 'histogram', 'displayer_args': { 'displayer': 'histogram' }}]\n",
    "\n",
    "    @classmethod\n",
    "    def style_columns(kls, col, sd):\n",
    "        digits = 3\n",
    "        if sd['is_integer']:\n",
    "            disp = {'displayer': 'float', 'min_fraction_digits':0, 'max_fraction_digits':0}\n",
    "        elif sd['is_numeric']:\n",
    "            disp = {'displayer': 'float', 'min_fraction_digits':digits, 'max_fraction_digits':digits}\n",
    "        else:\n",
    "            disp = {'displayer': 'obj'}\n",
    "        return {'col_name':col, 'displayer_args': disp }\n",
    "base_a_klasses = BuckarooWidget.analysis_klasses.copy()\n",
    "#base_a_klasses.extend([AdaptiveStyling, ValueCountPostProcessing])\n",
    "#class ABuckarooWidget(BuckarooWidget):\n",
    "#    analysis_klasses = base_a_klasses\n",
    "#acb = ABuckarooWidget(typed_df)\n",
    "#acb"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "27",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.8"
  },
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "state": {},
    "version_major": 2,
    "version_minor": 0
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
